<?php
/**
 * Created by PhpStorm.
 * User: t
 * Date: 2024/4/24
 * Time: 11:55
 */
namespace app\home\controller;
use app\home\model\Config;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Formatter;

class Test
{



    public function __construct()
    {

        $config = [
            'appid' => Config::getConfigValue('appid'),
            'appSecret' => Config::getConfigValue('appSecret'),
            'mchid' => Config::getConfigValue('mch_id'),
            'key_file' => Config::getConfigValue('key_file'),
            'cret_file' => Config::getConfigValue('cret_file'),
            'm_mch_id' =>  Config::getConfigValue('m_mch_id'),
            'm_appid' =>  Config::getConfigValue('m_appid'),
            'm_api_serial_number' => Config::getConfigValue('m_serial_number'),
            'serial_number' =>  Config::getConfigValue('serial_number'),
            'newResponseDataAddr' => APP_PATH.'/static/uploads/platform.json',
            'sslCertAddr' => APP_PATH.'/static/uploads/platform_cert.pem',
            'publicKey' => '',
            'publicKeyAddr' => APP_PATH.'/static/uploads/publicKeyAddr.pem',
            'apikey' => Config::getConfigValue('apikey'),
            'privateKey' =>'',
        ];
        $this->config = $config;
    }

    /**
     * getCertificates  下载平台证书 1.0
     * @return mixed
     */
    public function downloadCertificatesold()
    {
      /*  try {*/
            $url = "https://api.mch.weixin.qq.com/v3/certificates";
            // 请求随机串
            $nonce_str = $this->getRandChar();
            // 当前时间戳
            $timestamp = time();
            // 签名串
            $signContent = "GET\n/v3/certificates\n" . $timestamp . "\n" . $nonce_str . "\n\n";
            // 签名值
            $signature = $this->encryptSign($signContent);
            // 含有服务器用于验证商户身份的凭证
            $authorization = 'WECHATPAY2-SHA256-RSA2048 mchid="' . $this->config['mchid'] . '",nonce_str="' . $nonce_str . '",signature="' . $signature . '",timestamp="' . $timestamp . '",serial_no="' . $this->config['serial_number'] . '"';
            $curl_v        = curl_version();
            $header        = [
                'Accept:application/json',
                // 'Accept-Language:zh-CN',    // 默认 zh-CN 可以不填
                'Authorization:' . $authorization,
                'Content-Type:application/json',
                'User-Agent:curl/' . $curl_v['version'],
            ];
            $result        = $this->httpsRequest($url, NULL, $header);

            // print_r($result);die;
            $responseHeader = $this->parseHeaders($result[2]);
            $http_code      = $result[1];
            $responseBody   = json_decode($result[0], true);
            if ($http_code == 200 && !isset($responseBody['code'])) {
                $this->verifySignold($responseHeader, $result[0]);

            }
        /*} catch (\Exception $e) {

        }*/
    }

    /**
     * downloadCertificates 2.0
     * @return mixed
     * @throws WxException
     * @author   liuml  <liumenglei0211@163.com>
     * @DateTime 2019-03-05  11:03
     */
    public function downloadCertificates()
    {
        try {
            $data         = [
                'mch_id'    => $this->config['mchid'],
                'nonce_str' => $this->getRandChar(),
                'sign_type' => 'HMAC-SHA256',
                'sign'      => '',
            ];
            $data['sign'] = $this->makeSign($data, $data['sign_type']);
            $url          = self::WXAPIHOST . 'risk/getcertficates';
            $xml          = $this->toXml($data);
            // 发起入驻申请请求
            $result = $this->httpsRequest($url, $xml, [], true);
            // 处理返回值
            $rt = $this->disposeReturn($result, [
                'mch_id',
                'nonce_str',
                'sign',
                'result_code',
                'err_code',
                'err_code_des',
                'certificates',
            ]);
            if ($rt['result_code'] == 'SUCCESS') {
                return $this->verifySign($rt['certificates']);
            } else {
                throw new \Exception($rt['code'] . '----' . $rt['message']);
            }
        } catch (\Exception $e) {
            throw new WxException($e->getCode(), $e->getMessage());
        }
    }

    /**
     * setHashSign SHA256 with RSA 签名
     * @param $signContent
     * @return string
     */
    protected function encryptSign($signContent)
    {
        // 解析 key 供其他函数使用。

        $privateKey = openssl_get_privatekey($this->getPrivateKey());

        // 调用openssl内置签名方法，生成签名$sign
        openssl_sign($signContent, $sign, $privateKey, "SHA256");
        // 释放内存中私钥资源
        openssl_free_key($privateKey);
        $sign = base64_encode($sign);
        return $sign;
    }
    /**
     * verifyHashSign 校验签名 2.0
     * @param $data
     * @param $signature
     * @return int
     */
    protected function verifySign($responseBody)
    {
        $last_data = $this->newResponseData();
        $new_data  = json_decode($responseBody, true);
        $one       = false;
        if (empty($last_data)) {
            // 没有获取到上一次保存在本地的数据视为第一请求下载证书接口
            $serial_no = $this->getNewCertificates($new_data['data']);

            if($serial_no != ''){
                return $serial_no;
            }
        } else {
            $serial_no = $last_data['serial_no'];
        }

        $publicKey = $this->getPublicKey();
        if ($publicKey) {
            return $this->getNewCertificates($new_data['data'], $last_data);
        }
        return 0;
    }

    /**
     * verifyHashSign 校验签名 1.0
     * @param $data
     * @param $signature
     * @return int
     */
    protected function verifySignold($responseHeader, $responseBody)
    {
        $last_data = $this->newResponseData();
        $new_data  = json_decode($responseBody, true);

            $one = false;
            // 没有获取到上一次保存在本地的数据视为第一请求下载证书接口
            $serial_no = $this->getNewCertificates($new_data['data']);
            $one       = true;

            if (empty($last_data)) {} else {
            $serial_no = $last_data['serial_no'];
        }

        // 注 1：微信支付平台证书序列号位于 HTTP 头`Wechatpay-Serial`，验证签名前请先检查序列号是否跟商户所持有的微信支付平台证书序列号一致。（第一次从 1.1.5.中回包字段 serial_no 获取，非第一次时使用上次本地保存的平台证书序列号）
        if ($serial_no != $responseHeader['Wechatpay-Serial']) {
            if ($one)
                $this->clearFile();
            return 0;
        }
        $publicKey = $this->getPublicKey();
        if ($publicKey) {
            // 用微信支付平台证书公钥（第一次下载平台证书时从 1.1.5.中 “加密后的证书内容”进行解密获得。非第一次时使用上次本地保存的公钥）对“签名串”进行 SHA256 with RSA 签名验证
            $data              = $this->signatureValidation($responseHeader, $responseBody);
            $signature         = base64_decode($responseHeader['Wechatpay-Signature']);
            $publicKeyResource = openssl_get_publickey($publicKey);
            $f                 = openssl_verify($data, $signature, $publicKeyResource, "SHA256");
            openssl_free_key($publicKeyResource);
            if ($f == 1) {
                // 获取弃用日期最长证书
                return $this->getNewCertificates($new_data['data'], $last_data);
            }
            return $f;
        }
        return 0;
    }

    /**
     * signatureValidation 拼装校验签名串
     * @param $responseHeader
     * @param $responseBody
     * @return mixed
     */
    protected function signatureValidation($responseHeader, $responseBody)
    {
        return $responseHeader['Wechatpay-Timestamp'] . "\n" . $responseHeader['Wechatpay-Nonce'] . "\n" . $responseBody . "\n";
    }

    /**
     * getNewCertificates  获取弃用日期最长证书
     * @param array $data
     * @return false|int|string
     */
    protected function getNewCertificates(array $data, $last_data = '')
    {
        $key = 0;
        if (count($data) > 1) {
            $timeArr = [];
            foreach ($data as $k => $v) {
                $timeArr[$k] = strtotime($v['expire_time']);
            }
            $key = array_search(max($timeArr), $timeArr);
        }
        if (empty($last_data)) {
            $this->decryptCiphertext($data[$key]);
        } else {
            if (strtotime($last_data['expire_time']) < strtotime($data[$key]['expire_time'])) {
                $this->decryptCiphertext($data[$key]);
            } else {
                return $last_data['serial_no'];
            }
        }
        return $data[$key]['serial_no'];
    }

    /**
     * decryptCiphertext  AEAD_AES_256_GCM 解密加密后的证书内容得到平台证书的明文
     * @param $ciphertext
     * @param $ad
     * @param $nonce
     * @return string
     */
    protected function decryptCiphertext($data)
    {

        $encryptCertificate = $data['encrypt_certificate'];
        $ciphertext         = base64_decode($encryptCertificate['ciphertext']);
        $associated_data    = $encryptCertificate['associated_data'];
        $nonce              = $encryptCertificate['nonce'];

        // sodium_crypto_aead_aes256gcm_decrypt >=7.2版本，去php.ini里面开启下libsodium扩展就可以，之前版本需要安装libsodium扩展，具体查看php.net（ps.使用这个函数对扩展的版本也有要求哦，扩展版本 >=1.08）
        $plaintext = sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associated_data, $nonce, $this->config['apikey']);
        $this->savePublicKey($plaintext);
        $this->newResponseData($data);
        return true;
    }

    /**
     * getPrivateKey 超级管理员登录商户平台，在“账户中心”->“API 安全”->”API 证书（权威 CA 颁发）”中申请
     * API 商户证书，申请过程中会获取到私钥证书文件（申请流程详见 1.1.3.3“申请 API 商户证书“），打开
     * 文件获取私钥字符（定义变量 string sKey）
     * @return string
     */
    protected function getPrivateKey()
    {

        if (file_exists($this->config['key_file'])){
            $this->config['privateKey'] = file_get_contents($this->config['key_file']);
           return    $this->config['privateKey'];
        }

        return  file_get_contents($this->config['key_file']);

    }

    /**
     * savePublicKey 保存解密后的明文
     * @param $plaintext
     */
    protected function savePublicKey($plaintext)
    {
        $this->config['publicKey'] = $plaintext;
        file_put_contents($this->config['publicKeyAddr'], $plaintext);
        return $plaintext;
    }

    /**
     * getPublicKey 获取上一次本地保存的公钥
     * @return bool|string
     */
    protected function getPublicKey()
    {
        if (file_exists($this->config['publicKeyAddr']))
            return $this->config['publicKey'] ? : $this->config['publicKey'] = file_get_contents($this->config['publicKeyAddr']);
        return '';
    }

    /**
     * newResponseData 下载证书接口返回数据对比后最新的一次响应数据
     * @param $key
     * @param $data
     */
    protected function newResponseData(array $data = [])
    {
        if (!empty($data)) {

            $json = json_encode($data, JSON_UNESCAPED_UNICODE);
            if (file_put_contents($this->config['sslCertAddr'], $json)) {
                return true;
            }
            return false;
        }

        if (file_exists($this->config['sslCertAddr']))
            return json_decode(file_get_contents($this->config['sslCertAddr']), true);
        return [];
    }


    /**
     * clearFile 删除文件不需要的缓存文件
     * @param $str
     * @return bool
     */
    protected function clearFile()
    {
        unlink($this->config['sslCertAddr']);
        unlink($this->config['publicKeyAddr']);
    }

    /**
     * publicKeyEncrypt 对身份证等敏感信息加密
     * @param string $string
     * @return string
     * @throws WxException
     */
    protected function publicKeyEncrypt(string $string)
    {
        $crypted   = '';
        $publicKey = $this->getPublicKey();
        if ($publicKey) {
            $publicKeyResource = openssl_get_publickey($publicKey);
            $f                 = openssl_public_encrypt($string, $crypted, $publicKeyResource, OPENSSL_PKCS1_PADDING);
            openssl_free_key($publicKeyResource);
            if ($f) {
                return base64_encode($crypted);
            }
        }
        throw new WxException(20002);
    }

    /**
     * getRandChar 获取随机字符串
     * @param int $length
     * @return mixed
     */
    protected function httpsRequest($url, $data = '', array $headers = [], $userCert = false, $timeout = 30)
    {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        if (!empty($data)) {
            curl_setopt($curl, CURLOPT_POST, 1);
            curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        }
        //设置超时
        curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
        if ($userCert) {
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);//严格校验
            curl_setopt($curl, CURLOPT_SSLCERTTYPE, 'PEM');
            curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM');
            list($sslCertPath, $sslKeyPath) = $this->getSSLCertPath();
            curl_setopt($curl, CURLOPT_SSLCERT, $sslCertPath);
            curl_setopt($curl, CURLOPT_SSLKEY, $sslKeyPath);
        } else {
            if (substr($url, 0, 5) == 'https') {
                curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 信任任何证书
                curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); // 检查证书中是否设置域名
            }
        }
        if (!empty($headers)) {
            curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
            // curl_setopt($curl, CURLINFO_HEADER_OUT, true); //TRUE 时追踪句柄的请求字符串，从 PHP 5.1.3 开始可用。这个很关键，就是允许你查看请求header
            // $headers = curl_getinfo($curl, CURLINFO_HEADER_OUT); //官方文档描述是“发送请求的字符串”，其实就是请求的header。这个就是直接查看请求header，因为上面允许查看
        }
        curl_setopt($curl, CURLOPT_HEADER, true);    // 是否需要响应 header
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        $output          = curl_exec($curl);
        $header_size     = curl_getinfo($curl, CURLINFO_HEADER_SIZE);    // 获得响应结果里的：头大小
        $response_header = substr($output, 0, $header_size);    // 根据头大小去获取头信息内容
        $http_code       = curl_getinfo($curl, CURLINFO_HTTP_CODE);    // 获取响应状态码
        $response_body   = substr($output, $header_size);
        $error           = curl_error($curl);
        curl_close($curl);



        return [$response_body, $http_code, $response_header, $error];
    }

    /**
     * parseHeaders    处理curl响应头
     * @param $header
     * @return array
     */
    protected function parseHeaders($header)
    {
        $headers = explode("\r\n", $header);
        $head    = array();
        array_map(function($v) use (&$head) {
            $t = explode(':', $v, 2);
            if (isset($t[1])) {
                $head[trim($t[0])] = trim($t[1]);
            } else {
                if (!empty($v)) {
                    $head[] = $v;
                    if (preg_match("#HTTP/[0-9\.]+\s+([0-9]+)#", $v, $out)) {
                        $head['reponse_code'] = intval($out[1]);
                    }
                }
            }
        }, $headers);
        return $head;
    }

    /**
     * getRandChar 获取随机字符串
     * @param int $length
     * @return null|string
     */
    protected function getRandChar($length = 32)
    {
        $str    = NULL;
        $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
        $newStr = str_shuffle($strPol);
        $max    = strlen($strPol) - 1;
        for ($i = 0; $i < $length; $i++) {
            $str .= $newStr[mt_rand(0, $max)];    // rand($min,$max)生成介于min和max两个数之间的一个随机整数
        }
        return $str;
    }

    /**
     * MakeSign 生成签名
     * @param $data
     * @param string $signType
     * @return string
     */
    protected function makeSign(array $data, $signType = 'HMAC-SHA256')
    {

        //签名步骤一：按字典序排序参数
        ksort($data);

        $string = $this->toUrlParams($data);
        //签名步骤二：在string后加入KEY
        $string = $string . "&key=" . $this->diy_key;

        //签名步骤三：MD5加密或者HMAC-SHA256
        if ($signType == 'md5') {
            //如果签名小于等于32个,则使用md5验证
            $string = md5($string);
        } else {
            //是用sha256校验
            $string = hash_hmac("sha256", $string, $this->diy_key);
        }
        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    /**
     * ToUrlParams     格式化参数格式化成url参数
     * @param $data
     * @return string
     */
    protected function toUrlParams(array $data)
    {
        $buff = "";
        foreach ($data as $k => $v) {
            if ($k != "sign" && $v !== "" && !is_array($v)) {
                $buff .= $k . "=" . $v . "&";
            }
        }
        $buff = trim($buff, "&");
        return $buff;
    }

    /**
     * @param WxPayConfigInterface $config 配置对象
     * 检测签名
     */
    protected function checkSign($data)
    {
        strlen($data['sign']) <= 32 && $sign_type = 'md5';
        if ($this->makeSign($data, $sign_type ?? '') == $data['sign']) {
            return true;
        }
        throw new WxException(20000);
    }

    protected function getSSLCertPath()
    {
       // $wechatConfig = Config::get('api.wechatConfig');
        return [
            $this->config['sslCertAddr'],
            $this->config['key_file'],
        ];
    }

    /**
     * 输出xml字符
     * @throws WxPayException
     **/
    protected function toXml($data)
    {
        if (!is_array($data) || count($data) <= 0) {
            throw new WxException(30001);
        }

        $xml = "<xml>";
        foreach ($data as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }

    /**
     * 将xml转为array
     * @param string $xml
     * @throws WxPayException
     */
    protected function fromXml($xml)
    {
        if (!$xml) {
            throw new WxException(30000);
        }
        //将XML转为array
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $xml_parser = xml_parser_create();
        if (!xml_parse($xml_parser, $xml, true)) {
            xml_parser_free($xml_parser);
            throw new WxException(30000);
        } else {
            $arr = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        }
        return $arr;
    }

    /**
     * getMillisecond 获取毫秒级别的时间戳
     * @return float
     */
    protected function getMillisecond()
    {
        list($msec, $sec) = explode(' ', microtime());
        $msectime = (float) sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
        return $msectime;
    }

    /**
     * disposeReturn 处理微信小微商户接口返回值
     * @param $res  httpsRequest 方法调用接口返回的数组
     * @param array $need_fields 需要接口返回的字段（当return_code 、result_code都为SUCCESS时返回的字段的key）
     * @param array $arr 自定义的参数返回出去，例如入驻接口生成的业务编号
     * @return array 微信返回系统级错误不暴露出去，直接返回操作失败，业务级别错误返回具体错误消息
     */
    protected function disposeReturn($res, array $need_fields = [], array $arr = [])
    {
        if ($res[1] == 200) {
            $rt = $this->fromXml($res[0]);
            if ($rt['return_code'] != 'SUCCESS') {
                \Log::error($rt['return_msg']);
                throw new WxException(0, $rt['return_msg']);
            }
            if ($rt['result_code'] != 'SUCCESS') {
                throw new WxException(0, $rt['err_code_des'] ?? $rt['err_code_msg']);
            }
            if ($this->checkSign($rt)) {
                if (!empty($need_fields)) {
                    $need = [];
                    array_map(function($v) use ($rt, &$need) {
                        $need[$v] = $rt[$v] ?? '';
                    }, $need_fields);
                    return array_merge($need, $arr);
                }
                return $arr;
            }
        }
        throw new WxException(30002);
    }



}


